<xsl:stylesheet version='1.0'
  xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
  xmlns:terms="http://purl.org/dc/terms/"
  xmlns:xlink="http://www.w3.org/1999/xlink"
  xmlns:db="http://docbook.org/ns/docbook"
  xmlns:pbs="http://schema.pbs.gov.au"
  xmlns:group="http://schema.pbs.gov.au/Group/Type#"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  xmlns:skos='http://www.w3.org/2004/02/skos/core#'>

  <db:article>
    <db:info>
      <db:title>Extract Prescriber Formulary</db:title>

      <db:copyright>
        <db:year>2012</db:year>
        <db:year>2011</db:year>
        <db:year>2010</db:year>
        <db:holder>Commonwealth of Australia</db:holder>
      </db:copyright>

      <db:legalnotice>
        <db:para>This software is copyrighted by the Commonwealth of Australia ("The Commonwealth").  The following terms apply to all files associated with the software unless explicitly disclaimed in individual files.</db:para>
        <db:para>Permission is hereby granted to use, copy, modify, distribute, and license this software and its documentation for any purpose, provided that existing copyright notices are retained in all copies and that this notice is included verbatim in any distributions. No written agreement, license, or royalty fee is required for any of the authorized uses. Modifications to this software may be copyrighted by their authors and need not follow the licensing terms described here, provided that the new terms are clearly indicated on the first page of each file where they apply.</db:para>
        <db:para>IN NO EVENT SHALL THE COMMONWEALTH OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</db:para>
        <db:para>THE COMMONWEALTH SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, AND THE COMMONWEALTH HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.</db:para>
      </db:legalnotice>

      <db:author>
        <db:personname>Steve Ball</db:personname>
        <db:email>Steve.Ball@health.gov.au</db:email>
        <db:affiliation>
          <db:orgname>Department of Health and Ageing</db:orgname>
          <db:orgdiv>PharmBiz</db:orgdiv>
        </db:affiliation>
      </db:author>

      <db:revhistory>
        <db:revision>
          <db:revnumber>1.2.1</db:revnumber>
          <db:date>2012-05-16</db:date>
          <db:authorinitials>SRB</db:authorinitials>
          <db:revremark>Added "EP" program code.</db:revremark>
        </db:revision>
        <db:revision>
          <db:revnumber>1.2</db:revnumber>
          <db:date>2011-06-24</db:date>
          <db:authorinitials>SRB</db:authorinitials>
          <db:revremark>Updated for PBS XML v2.5.</db:revremark>
        </db:revision>
        <db:revision>
          <db:revnumber>1.1</db:revnumber>
          <db:date>2011-01-21</db:date>
          <db:authorinitials>SRB</db:authorinitials>
          <db:revremark>Updated for PBS XML v2.4.</db:revremark>
        </db:revision>
        <db:revision>
          <db:revnumber>1.0</db:revnumber>
          <db:date>2010-03-23</db:date>
          <db:authorinitials>SRB</db:authorinitials>
          <db:revremark>Initial version.</db:revremark>
        </db:revision>
      </db:revhistory>
    </db:info>

    <db:para>This stylesheet extracts the formulary for a given prescriber type from the PBS XML document.</db:para>
    <db:para>The source document is the PBS XML document, schema-valid to v2.4 (or later) of the PBS XML Schema.</db:para>
    <db:para>The result is text. Each line lists the item code of an item in the formulary for the given prescriber type.</db:para>
  </db:article>

  <xsl:output method='text'/>

  <db:param>The URI of the prescriber type.</db:param>
  <xsl:param name='prescriber.uri'/>

  <db:param>The code (as used by Medicare Australia) of the prescriber type. Either the URI or the code may be given as a parameter, not both.</db:param>
  <xsl:param name='prescriber.code'/>

  <db:param>What mode to operate in. Allowed values are: <db:literal>auto</db:literal>, <db:literal>full</db:literal> or <db:literal>update</db:literal>. <db:literal>full</db:literal> mode (the default) produces a full list of prescriber types. <db:literal>update</db:literal> mode compares this schedule to the previous schedule and produces a list of prescriber types that have been added or deleted. <db:literal>update</db:literal> mode requires the <db:tag>previous-schedule</db:tag> parameter to be specified. <db:literal>auto</db:literal> mode attempts to discover if the previous schedule is available; if it is then it switches to <db:literal>update</db:literal> mode, if not then <db:literal>full</db:literal> mode is used.</db:param>
  <xsl:param name='mode' select='"full"'/>

  <db:param>The URL for the previous schedule. This parameter is required for <db:literal>update</db:literal> mode, but is otherwise ignored.</db:param>
  <xsl:param name='previous-schedule'/>
  <xsl:variable name='prev' select='document($previous-schedule)'/>

  <db:param>What information to display, and the order in which to display it. Allowed values are: <db:literal>drug-name</db:literal>, <db:literal>item-code</db:literal> and <db:literal>prescriber-type</db:literal>.</db:param>
  <xsl:param name='display'>
drug-name
item-code
  </xsl:param>

  <xsl:variable name='meta-group'
    select='"http://schema.pbs.gov.au/Group#prescriber-types"'/>

  <xsl:key name='pr-by-group'
    match='pbs:prescribing-rule'
    use='pbs:member-of-list/pbs:member-of/@xlink:href'/>
  <xsl:key name='pr-by-code'
    match='pbs:prescribing-rule'
    use='pbs:code'/>
  <xsl:key name='group-by-uri'
    match='pbs:group'
    use='pbs:name'/>
  <xsl:key name='group-by-id'
    match='pbs:group'
    use='concat("#", @xml:id)'/>
  <xsl:key name='skos-by-label'
    match='skos:Concept'
    use='skos:prefLabel'/>
  <xsl:key name='ident'
    match='*'
    use='@xml:id'/>

  <xsl:template match='/'>
    <xsl:choose>
      <xsl:when test='$prescriber.uri = "" and
                      $prescriber.code = ""'>
        <xsl:message terminate='yes'>Either the URI or code of the prescriber type must be specified. Unable to continue.</xsl:message>
      </xsl:when>
      <xsl:when test='$prescriber.uri != "" and
                      $prescriber.code != ""'>
        <xsl:message terminate='yes'>A URI and code for the prescriber type have been specified. Only one may be specified. Unable to continue.</xsl:message>
      </xsl:when>
    </xsl:choose>

    <xsl:variable name='prescriber'>
      <xsl:choose>
        <xsl:when test='$prescriber.uri != "" and
                        key("group-by-uri", $prescriber.uri) and
                        key("group-by-uri", $prescriber.uri)[key("group-by-id", pbs:member-of-list/pbs:member-of/@xlink:href)/pbs:name = $meta-group]'>
          <!-- NB. the above test assumes that prescriber groups are only members of the meta-group and no other groups -->
          <xsl:value-of select='$prescriber.uri'/>
        </xsl:when>
        <xsl:when test='$prescriber.uri != ""'>
          <xsl:message terminate='yes'>URI "<xsl:value-of select='$prescriber.uri'/>" does not specify a prescriber group. Unable to continue.</xsl:message>
        </xsl:when>
        <xsl:when test='key("skos-by-label", $prescriber.code) and
                        key("group-by-uri", key("skos-by-label", $prescriber.code)/@rdf:about)[key("group-by-id", pbs:member-of-list/pbs:member-of/@xlink:href)/pbs:name = $meta-group]'>
          <xsl:value-of select='key("skos-by-label", $prescriber.code)/@rdf:about'/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:message terminate='yes'>Unable to determine the prescriber group.</xsl:message>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:variable>

    <xsl:variable name='schedule-date-year'
      select='substring(/pbs:root/pbs:info/terms:valid[1], 1, 4)'/>
    <xsl:variable name='schedule-date-month'
      select='substring(/pbs:root/pbs:info/terms:valid[1], 6, 2)'/>

    <xsl:choose>
      <xsl:when test='$mode = "auto" and
                      ($schedule-date-year = "" or
                      $schedule-date-month = "" or
                      translate($schedule-date-year, "0123456789", "") != "" or
                      translate($schedule-date-month, "0123456789", "") != "")'>
        <xsl:message terminate='yes'>auto mode cannot determine schedule date</xsl:message>
      </xsl:when>
      <xsl:when test='$mode = "auto"'>
        <xsl:variable name='previous-year'>
          <xsl:choose>
            <xsl:when test='$schedule-date-month = 1'>
              <xsl:value-of select='$schedule-date-year - 1'/>
            </xsl:when>
            <xsl:otherwise>
              <xsl:value-of select='$schedule-date-year'/>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:variable>
        <xsl:variable name='previous-month'>
          <xsl:choose>
            <xsl:when test='$schedule-date-month = 1'>12</xsl:when>
            <xsl:otherwise>
              <xsl:value-of select='format-number($schedule-date-month - 1, "00")'/>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:variable>
 
        <xsl:variable name='previous-schedule'
          select='concat("G2B ", $previous-year, $previous-month, "01.xml")'/>

        <xsl:variable name='prev' select='document($previous-schedule)'/>

        <xsl:choose>
          <xsl:when test='$prev'>
            <!-- switch to mode "update" -->
            <xsl:call-template name='update'>
              <xsl:with-param name='prescriber' select='$prescriber'/>
              <xsl:with-param name='prev' select='$prev'/>
              <xsl:with-param name='mode'>update</xsl:with-param>
            </xsl:call-template>
          </xsl:when>
          <xsl:otherwise>
            <!-- switch to mode "full" -->
            <xsl:call-template name='full'>
              <xsl:with-param name='prescriber' select='$prescriber'/>
              <xsl:with-param name='mode'>full</xsl:with-param>
            </xsl:call-template>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:when>

      <xsl:when test='$mode = "update"'>
        <xsl:call-template name='update'>
          <xsl:with-param name='prescriber' select='$prescriber'/>
          <xsl:with-param name='prev' select='$prev'/>
          <xsl:with-param name='mode'>update</xsl:with-param>
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:call-template name='full'>
          <xsl:with-param name='prescriber' select='$prescriber'/>
          <xsl:with-param name='mode'>full</xsl:with-param>
        </xsl:call-template>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template name='full'>
    <xsl:param name='prescriber'/>
    <xsl:param name='prev' select='/..'/>
    <xsl:param name='mode' select='"full"'/>

    <xsl:apply-templates select='key("pr-by-group", concat("#", key("group-by-uri", $prescriber)/@xml:id))'>
      <xsl:with-param name='other' select='$prev'/>
      <xsl:with-param name='mode' select='"full"'/>
    </xsl:apply-templates>
  </xsl:template>
  <xsl:template name='update'>
    <xsl:param name='prescriber'/>
    <xsl:param name='prev' select='/..'/>
    <xsl:param name='mode' select='"update"'/>

    <xsl:if test='not($prev)'>
      <xsl:message terminate='yes'>unable to open previous month's schedule</xsl:message>
    </xsl:if>

    <xsl:text>ADDITIONS&#xa;&#xa;</xsl:text>

    <!-- First look in the current document (the current schedule).
         For each item, see if the corresponding item has the prescriber type
         in the previous schedule.
      -->

    <xsl:apply-templates select='key("pr-by-group", concat("#", key("group-by-uri", $prescriber)/@xml:id))'>
      <xsl:with-param name='other' select='$prev'/>
      <xsl:with-param name='mode' select='"update"'/>
      <xsl:with-param name='prescriber' select='$prescriber'/>
    </xsl:apply-templates>

    <xsl:text>&#xa;&#xa;DELETIONS&#xa;&#xa;</xsl:text>

    <!-- Now look in the previous document (last month's schedule).
         For each item, see if the corresponding item has the prescriber type
         in the current schedule.
      -->

    <xsl:variable name='root' select='/'/>
    <xsl:for-each select='$prev'>
      <xsl:apply-templates select='key("pr-by-group", concat("#", key("group-by-uri", $prescriber)/@xml:id))'>
        <xsl:with-param name='other' select='$root'/>
        <xsl:with-param name='mode' select='"update"'/>
        <xsl:with-param name='prescriber' select='$prescriber'/>
      </xsl:apply-templates>
    </xsl:for-each>
  </xsl:template>

  <db:template>
    <db:info>
      <db:title>Prescribing Rules</db:title>
    </db:info>
    <db:para>This template displays the prescriber types for a prescribing rule. In <db:literal>full</db:literal> mode, the prescriber types are displayed. In <db:literal>update</db:literal> mode, the prescriber types are compared to either the previous schedule (for additions) or current schedule (for deletions) and is only displayed if it is different in the other document.</db:para>
    <db:para>This template works for both additions and deletions. The only difference is which document is being checked for the corresponding prescribing rule. The document to compare against is given in the <db:literal>other</db:literal> parameter.</db:para>
  </db:template>
  <xsl:template match='pbs:prescribing-rule'>
    <xsl:param name='other' select='/..'/>
    <xsl:param name='mode'/>
    <xsl:param name='prescriber'/>

    <xsl:choose>
      <xsl:when test='$mode = "update"'>
        <xsl:variable name='this' select='.'/>
        <xsl:variable name='other-exists'>
          <xsl:for-each select='$other'>
            <xsl:if test='key("pr-by-code", $this/pbs:code)'>
              <xsl:text>exists</xsl:text>
            </xsl:if>
          </xsl:for-each>
        </xsl:variable>
        <xsl:choose>
          <xsl:when test='$other-exists = ""'>
            <!-- The item has been added to the schedule -->
            <xsl:call-template name='display'>
              <xsl:with-param name='what' select='$display'/>
            </xsl:call-template>
            <xsl:text>&#xa;</xsl:text>
          </xsl:when>
          <xsl:otherwise>
            <!-- See if the prescriber group has been added -->
            <xsl:variable name='other-types'>
              <xsl:for-each select='$other'>
                <xsl:call-template name='get-type-list'>
                  <xsl:with-param name='node'
                    select='key("pr-by-code", $this/pbs:code)'/>
                </xsl:call-template>
              </xsl:for-each>
            </xsl:variable>
            <xsl:if test='not(contains($other-types, $prescriber))'>
              <xsl:call-template name='display'>
                <xsl:with-param name='what' select='$display'/>
              </xsl:call-template>
              <xsl:text>&#xa;</xsl:text>
            </xsl:if>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:when>
      <xsl:otherwise>
        <xsl:call-template name='display'>
          <xsl:with-param name='what' select='$display'/>
        </xsl:call-template>
        <xsl:text>&#xa;</xsl:text>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <db:template name='display'>
    <db:refpurpose>Display values</db:refpurpose>
    <db:para>This template implements a generalised mechanism for displaying values from the data.  The <db:literal>what</db:literal> parameter controls what values are included and in what order.</db:para>
  </db:template>
  <xsl:template name='display'>
    <xsl:param name='what'/>
    <xsl:param name='prefix'/>

    <xsl:choose>
      <xsl:when test='normalize-space($what) = ""'/>
      <xsl:when test='contains($what, "&#xa;") and
                      normalize-space(substring-before($what, "&#xa;")) = ""'>
        <xsl:call-template name='display'>
          <xsl:with-param name='what'
            select='substring-after($what, "&#xa;")'/>
          <xsl:with-param name='prefix' select='$prefix'/>
        </xsl:call-template>
      </xsl:when>
      <xsl:when test='contains($what, "&#xa;")'>
        <xsl:call-template name='display-one'>
          <xsl:with-param name='item'
            select='normalize-space(substring-before($what, "&#xa;"))'/>
          <xsl:with-param name='prefix' select='$prefix'/>
        </xsl:call-template>

        <xsl:call-template name='display'>
          <xsl:with-param name='what'
            select='substring-after($what, "&#xa;")'/>
          <xsl:with-param name='prefix' select='"&#9;"'/>
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:call-template name='display-one'>
          <xsl:with-param name='item'
            select='normalize-space($what)'/>
          <xsl:with-param name='prefix' select='$prefix'/>
        </xsl:call-template>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <db:template name='display-one'>
    <db:refpurpose>Display a value</db:refpurpose>
    <db:para>Display a single data value.</db:para>
  </db:template>
  <xsl:template name='display-one'>
    <xsl:param name='item'/>
    <xsl:param name='prefix'/>
    <xsl:param name='pr' select='.'/>

    <xsl:choose>
      <xsl:when test='$item = ""'/>

      <xsl:when test='$item = "drug-name" and
                      $pr/pbs:ready-prepared'>
        <xsl:value-of select='$prefix'/>
        <xsl:variable name='mpp'
          select='key("ident", substring-after($pr/pbs:ready-prepared/pbs:mpp-reference/@xlink:href, "#"))'/>
        <xsl:apply-templates select='$mpp/../db:title' mode='textonly'/>
        <xsl:text>, </xsl:text>
        <xsl:apply-templates select='$mpp/db:title'
          mode='textonly'/>
      </xsl:when>
      <xsl:when test='$item = "drug-name" and
                      $pr/pbs:infusible'>
        <xsl:value-of select='$prefix'/>
        <xsl:variable name='mp'
          select='key("ident", substring-after($pr/pbs:infusible/pbs:mp-reference/@xlink:href, "#"))'/>
        <xsl:apply-templates select='$mp/db:title' mode='textonly'/>
        <xsl:text>, </xsl:text>
        <xsl:value-of select='$pr/pbs:infusible/pbs:maximum-quantity/@amount'/>
        <xsl:text> </xsl:text>
        <xsl:value-of select='$pr/pbs:infusible/pbs:maximum-quantity/@unit'/>
      </xsl:when>
      <xsl:when test='$item = "drug-name" and
                      ($pr/pbs:standard-formula-preparation |
                      $pr/pbs:drug-tariff |
                      $pr/pbs:extemporaneous-preparation)'>
        <xsl:value-of select='$prefix'/>
        <xsl:apply-templates select='$pr/pbs:standard-formula-preparation |
                                     $pr/pbs:drug-tariff |
                                     $pr/pbs:extemporaneous-preparation'
          mode='sfp'/>
      </xsl:when>
      <xsl:when test='$item = "item-code"'>
        <xsl:value-of select='$prefix'/>
        <xsl:apply-templates select='$pr/pbs:code'
          mode='textonly'/>
      </xsl:when>
    </xsl:choose>
  </xsl:template>

  <xsl:template match='pbs:standard-formula-preparation |
                       pbs:drug-tariff |
                       pbs:extemporaneous-preparation'
    mode='sfp'>
    <xsl:apply-templates select='db:title' mode='textonly'/>
    <xsl:if test='pbs:maximum-quantity'>
      <xsl:text>, </xsl:text>
      <xsl:apply-templates select='pbs:maximum-quantity' mode='sfp'/>
    </xsl:if>
  </xsl:template>
  <xsl:template match='pbs:maximum-quantity'
    mode='sfp'>
    <xsl:apply-templates mode='textonly'/>
    <xsl:apply-templates select='@unit' mode='textonly'/>
  </xsl:template>

  <db:template name='get-type-list'>
    <db:refpurpose>Determine prescriber types</db:refpurpose>
    <db:para>Returns a space-separated list of prescriber type codes for a prescribing rule.</db:para>
  </db:template>
  <xsl:template name='get-type-list'>
    <xsl:param name='node' select='/..'/>

    <xsl:choose>
      <xsl:when test='$node/*/pbs:member-of-list/pbs:member-of'>
        <!-- Make sure at least one of the groups is a prescriber group -->
        <xsl:variable name='prescriber-groups'
          select='$node/*/pbs:member-of-list/pbs:member-of[key("group-by-id", key("group-by-id", @xlink:href)/pbs:member-of-list/pbs:member-of/@xlink:href)/pbs:name = $meta-group]'/>

        <xsl:choose>
          <xsl:when test='$prescriber-groups'>
            <xsl:for-each select='$prescriber-groups'>
              <xsl:value-of select='key("group-by-id", @xlink:href)/pbs:name'/>
              <xsl:text> </xsl:text>
            </xsl:for-each>
          </xsl:when>
          <xsl:otherwise>
            <xsl:call-template name='infer-type'>
              <xsl:with-param name='node' select='$node'/>
            </xsl:call-template>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:when>
      <xsl:otherwise>
        <!-- No group membership information has been given, so infer prescriber type from the schedule category -->
        <xsl:call-template name='infer-type'>
          <xsl:with-param name='node' select='$node'/>
        </xsl:call-template>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template name='infer-type'>
    <xsl:param name='node' select='.'/>

    <xsl:choose>
      <xsl:when test='$node/ancestor::pbs:listings-list[pbs:info/pbs:code = "GE"] |
                      $node/ancestor::pbs:listings-list[pbs:info/pbs:code = "EP"] |
                      $node/ancestor::pbs:listings-list[pbs:info/pbs:code = "SB"] |
                      $node/ancestor::pbs:listings-list[pbs:info/pbs:code = "DB"] |
                      $node/ancestor::pbs:listings-list[pbs:info/pbs:code = "PL"] |
                      $node/ancestor::pbs:listings-list[pbs:info/pbs:code = "PQ"] |
                      $node/ancestor::pbs:listings-list[pbs:info/pbs:code = "R1"] |
                      $node/ancestor::pbs:listings-list[pbs:info/pbs:code = "HS"] |
                      $node/ancestor::pbs:listings-list[pbs:info/pbs:code = "IF"] |
                      $node/ancestor::pbs:listings-list[pbs:info/pbs:code = "MD"] |
                      $node/ancestor::pbs:listings-list[pbs:info/pbs:code = "MF"] |
                      $node/ancestor::pbs:listings-list[pbs:info/pbs:code = "GH"] |
                      $node/ancestor::pbs:listings-list[pbs:info/pbs:code = "CS"] |
                      $node/ancestor::pbs:listings-list[pbs:info/pbs:code = "CT"]'>
        <xsl:text>http://schema.pbs.gov.au/Group/Type#medical</xsl:text>
      </xsl:when>
      <xsl:when test='$node/ancestor::pbs:listings-list[pbs:info/pbs:code = "DT"] |
                      $node/ancestor::pbs:listings-list[pbs:info/pbs:code = "DS"]'>
        <xsl:text>http://schema.pbs.gov.au/Group/Type#dental</xsl:text>
      </xsl:when>
      <xsl:when test='$node/ancestor::pbs:listings-list[pbs:info/pbs:code = "OT"]'>
        <xsl:text>http://schema.pbs.gov.au/Group/Type#optometrical</xsl:text>
      </xsl:when>
      <xsl:when test='$node/pbs:standard-formula-preparation'>
        <xsl:text>http://schema.pbs.gov.au/Group/Type#medical</xsl:text>
      </xsl:when>
      <xsl:otherwise>
        <xsl:message>Item <xsl:value-of select='$node/pbs:code'/> has unknown prescriber type</xsl:message>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

</xsl:stylesheet>
